package service

import (
	rich_err "app/error"
	"app/models"
	"errors"
	"fmt"
	"math"
	"net/url"
	"reflect"
	"strconv"
	"strings"

	"github.com/GoAdminGroup/go-admin/modules/logger"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
)

func BuildWhereConditions[T any](queryParams url.Values, prefix string, alias map[string]string) ([]string, []interface{}) {
	var conditions []string
	var values []interface{}

	//conditions = append(conditions, prefix+"."+"id > ?")
	//values = append(values, "0")

	var result T
	typ := reflect.TypeOf(result)

	// Collect matching query parameters and their values
	matches := make(map[string]string)
	tagToFieldName := make(map[string]*reflect.StructField)
	collectMatchingFields(typ, queryParams, matches, tagToFieldName, prefix, false)

	// Generate conditions and values from collected matches
	for jsonTag, value := range matches {
		field, ok := tagToFieldName[jsonTag]
		if !ok {
			logger.Infof("field '%s' not found in type %s", jsonTag, typ)
			continue
		}

		if realField, ok := alias[jsonTag]; ok {
			jsonTag = realField
		}

		if field.Type.Kind() == reflect.String {
			conditions = append(conditions, fmt.Sprintf("%s LIKE ?", jsonTag))
			values = append(values, "%"+value+"%")
		} else if field.Type.Kind() == reflect.Bool {
			conditions = append(conditions, fmt.Sprintf("%s = ?", jsonTag))
			values = append(values, value == "true")
		} else {
			conditions = append(conditions, fmt.Sprintf("%s = ?", jsonTag))
			values = append(values, value)
		}
	}

	return conditions, values
}

// collectMatchingFields collects matching query parameters for a given type.
func collectMatchingFields(typ reflect.Type, queryParams url.Values, matches map[string]string, tagToFieldName map[string]*reflect.StructField, prefix string, needPrefix bool) {
	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)

		if field.Type.Kind() == reflect.Struct {
			collectMatchingFields(field.Type, queryParams, matches, tagToFieldName, prefix, true)
			continue
		}

		jsonTag := field.Tag.Get("json")

		if jsonTag == "" {
			continue
		}
		newTag := jsonTag
		if needPrefix && !strings.HasPrefix(newTag, prefix+".") {
			newTag = prefix + "." + newTag
		}

		if _, ok := queryParams[jsonTag]; ok {
			matches[newTag] = queryParams.Get(jsonTag)
		}

		tagToFieldName[newTag] = &field

	}
}

func BuildOrder[T any](queryParams url.Values, orm *gorm.DB, prefix string) *gorm.DB {
	order := queryParams.Get("order")
	if order == "" {
		return orm
	}
	orderField := strings.TrimLeft(order, "-")
	var result T
	typ := reflect.TypeOf(result)
	resultField := collectOrderFields(typ, orderField, prefix, false)
	if resultField == "" {
		return orm
	}
	direction := "ASC"

	if strings.HasPrefix(order, "-") {
		direction = "DESC"
	}

	orm = orm.Order(fmt.Sprintf("%s %s", resultField, direction))

	return orm
}

// collectOrderFields collects nested order fields for a given type and order field.
func collectOrderFields(typ reflect.Type, orderField, prefix string, needPrefix bool) string {
	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)

		if field.Type.Kind() == reflect.Struct {
			newTag := collectOrderFields(field.Type, orderField, prefix, true)
			if newTag != "" {
				return newTag
			} else {
				continue
			}
		}
		jsonTag := field.Tag.Get("json")

		if jsonTag == "" {
			continue
		}

		if jsonTag != orderField {
			continue
		}
		newTag := jsonTag
		if needPrefix && !strings.HasPrefix(jsonTag, prefix) {
			newTag = prefix + "." + jsonTag
		}
		return newTag
	}
	return ""
}

func BuildPage(queryParams url.Values, orm *gorm.DB) (*gorm.DB, *models.Pager) {
	// Extract the page_index and page_size values from the query parameters
	pageIndex, err := strconv.Atoi(queryParams.Get("page_index"))
	if err != nil || pageIndex <= 0 {
		pageIndex = 1 // Default value is 1
	}

	pageSize, err := strconv.Atoi(queryParams.Get("page_size"))
	if err != nil || pageSize <= 0 {
		pageSize = 10 // Default value is 10
	}

	countBase := orm
	countBase.Count(&countBase.RowsAffected)
	pager := &models.Pager{
		Count: int(countBase.RowsAffected),
	}
	pager.Size = pageSize
	pager.Index = pageIndex

	if pager.Count > 0 {
		totalPages := int(math.Ceil(float64(pager.Count) / float64(pageSize)))
		if pager.Index > totalPages {
			pager.Index = totalPages
		}
		offset := (pager.Index - 1) * pager.Size
		orm = orm.Offset(offset).Limit(pager.Size)
	}
	return orm, pager
}

func Gets[T any](c *gin.Context, prefix string) ([]*T, *models.Pager, error) {
	// Retrieve the query parameters from the context
	queryParams := c.Request.URL.Query()
	// Build the WHERE conditions based on the query parameters
	conditions, values := BuildWhereConditions[T](queryParams, prefix, map[string]string{})
	// Query the database to retrieve the data
	var results []*T
	base := models.Orm.Select("*")
	if len(conditions) > 0 {
		base = base.Where(strings.Join(conditions, GetQueryMode(c)), values...)
	}

	base = BuildOrder[T](queryParams, base, prefix)
	base, pager := BuildPage(queryParams, base)
	err := base.Find(&results).Error
	if err != nil {
		// Handle the error
		return nil, nil, err
	}
	return results, pager, nil
}

func Get[T any](context *gin.Context) (*T, error) {
	// Retrieve the id parameter from the URL path
	id := context.Param("id")
	if id == "" {
		return nil, fmt.Errorf("id parameter is required")
	}

	var record T
	err := models.Orm.First(&record, id).Error
	if err != nil {
		// Handle the error
		return nil, err
	}

	return &record, nil
}

func GetDetails[T any](context *gin.Context, base *gorm.DB, prefix string, alias map[string]string) ([]*T, *models.Pager, error) {
	queryParams := context.Request.URL.Query()

	conditions, values := BuildWhereConditions[T](queryParams, prefix, alias)
	var results []*T
	base = base.Where(strings.Join(conditions, GetQueryMode(context)), values...)
	base = BuildOrder[T](queryParams, base, prefix)
	base, pager := BuildPage(queryParams, base)

	err := base.Find(&results).Error
	if err != nil {
		return nil, nil, err
	}

	return results, pager, nil
}

func GetDetail[T any](id string, base *gorm.DB) (*T, error) {
	var record T
	err := base.First(&record, id).Error
	if err != nil {
		// Handle the error
		return nil, err
	}

	return &record, nil
}

func GetQueryMode(context *gin.Context) string {
	queryMode := context.Query("query_mode")
	if queryMode == "" || (strings.ToLower(queryMode) != "and" && strings.ToLower(queryMode) != "or") {
		queryMode = " AND "
	}
	return " " + strings.TrimSpace(queryMode) + " "
}

func DeleteByConditions[T any](conditions map[string]interface{}) *rich_err.RichError {
	modelType := reflect.TypeOf((*T)(nil)).Elem()
	modelValue := reflect.New(modelType).Elem()
	tableName := models.Orm.NewScope(modelValue.Addr().Interface()).TableName()
	base := models.Orm.Table(tableName)
	for condition, val := range conditions {
		base = base.Where(condition, val) // 更新 base
	}
	result := base.Delete(nil)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil
		}
		return rich_err.NewError(rich_err.DatabaseFailed, result.Error)
	}

	return nil
}
